import telebot
from dotenv import load_dotenv
import os
import logging

from telebot import asyncio_filters
from telebot.async_telebot import AsyncTeleBot
from telebot.asyncio_handler_backends import StatesGroup, State
from telebot.asyncio_storage import StateMemoryStorage
import asyncio
from datetime import datetime, timedelta
import mysql.connector

from telethon import TelegramClient

load_dotenv()
logging.basicConfig(filename='sender.log', format='%(asctime)s;%(levelname)s;%(message)s',
                    datefmt='%d.%m.%Y %H:%M:%S', level=logging.INFO)
logging.getLogger('telethon').setLevel(level=logging.ERROR)
logger = telebot.logger
telebot.logger.setLevel(logging.ERROR)
DELAY = int(os.environ['DELAY'])
DELAY2 = int(os.environ['DELAY2'])
DELAY3 = int(os.environ['DELAY3'])
TOKEN = os.environ['TOKEN']
API_ID = int(os.environ['API_ID'])
API_HASH = os.environ['API_HASH']
HOST = os.environ['HOST']
USERNAME = os.environ['USERNAME']
PASSWORD = os.environ['PASSWORD']
DATABASE = os.environ['DATABASE']

state_storage = StateMemoryStorage()
bot = AsyncTeleBot(TOKEN, parse_mode="MARKDOWN", state_storage=state_storage)
user_data = {}  # Ключ - username, значение - Entry


class MyStates(StatesGroup):
    # Just name variables differently
    adding_entry = State()


class Entry:
    def __init__(self, to: list, text: str, start: datetime, delay: int = DELAY):
        self.start = start - timedelta(minutes=delay)
        self.to = to
        self.text = text
        self.delay = delay

    def __str__(self):
        return "{}\n{}\nЗадержка отправки: {} минут".format("\n".join(self.to), self.text, self.delay)


async def send_loop():
    async with TelegramClient('session', API_ID, API_HASH) as client:
        dialogs = await client.get_dialogs()
    await asyncio.sleep(DELAY3)
    while True:
        async with TelegramClient('session', API_ID, API_HASH) as client:
            for user in user_data:
                entries = user_data[user]
                for e in entries:
                    start_time = e.start
                    now = datetime.now()
                    minutes_left = (now - start_time).seconds // 60
                    if minutes_left >= e.delay:
                        e.start = now
                        for r in e.to:
                            try:
                                await client.send_message(r, e.text)
                                logging.info('Send message to {}: {}'.format(r, e.text))
                                await asyncio.sleep(DELAY2)
                            except ValueError as e:
                                logging.error("Can't find entity {}. {}".format(r, e))
        await asyncio.sleep(DELAY3)


async def get_username_and_urn(id: str) -> (str, int):
    cnx = mysql.connector.connect(user=USERNAME, password=PASSWORD,
                                  host=HOST,
                                  database=DATABASE)
    cursor = cnx.cursor()
    cursor.execute("SELECT user_name, urn FROM ba WHERE user_id={}".format(id))
    result = cursor.fetchone()
    cnx.close()
    if result is None:
        return None
    return result[0], int(result[1])


def is_authed(msg) -> bool:
    return msg.from_user.username in user_data


@bot.message_handler(state="*", commands=['cancel'])
async def cancel(message):
    if is_authed(message):
        await bot.delete_state(message.from_user.id, message.chat.id)


@bot.message_handler(commands=['auth'])
async def auth(message):
    if is_authed(message):
        return
    user_id = message.text.strip().split(' ', 1)
    if len(user_id) < 2:
        await bot.reply_to(message, "Неправильный формат команды. Попробуйте снова")
        return
    user_id = user_id[1]
    ans = await get_username_and_urn(user_id)
    if ans is None:
        await bot.reply_to(message, "Такого пользователя не существует")
        return
    username, urn = ans
    if message.from_user.username == username and urn > 0:
        user_data[username] = []
        await bot.delete_my_commands(scope=None, language_code=None)

        await bot.set_my_commands(
            commands=[
                telebot.types.BotCommand("/start", "Приветсвенное сообщение"),
                telebot.types.BotCommand("/add", "Добавить запись"),
                telebot.types.BotCommand("/show", "Посмотреть все записи"),
                telebot.types.BotCommand("/delete", "Удалить запись по ее номеру указанному в /show"),
                telebot.types.BotCommand('/cancel', "Отменить добавление"),
                telebot.types.BotCommand("/help", "Помощь")
            ],
        )
        await bot.reply_to(message,
                           "Вы успешно авторизировались, {}. Введите /help, чтобы узнать все возможности бота и "
                           "получить меню команд".format(
                               username))

    else:
        await bot.reply_to(message, "Неправильный id или вас нет в базе данных или ваш urn <= 0")


@bot.message_handler(state=MyStates.adding_entry)
async def add_entry(message):
    body = message.text
    splited = body.split('\n\n')
    if len(splited) < 2:
        await bot.reply_to(message, "Не корректный формат сообщения")
        return
    to, text, *tail = splited
    username = message.from_user.username
    if len(splited) > 2 and splited[2].isdigit():
        entry = Entry(list(map(lambda m: m.strip(), to.split('\n'))), text, datetime.now(), int(splited[2]))
    else:
        entry = Entry(list(map(lambda m: m.strip(), to.split('\n'))), text, datetime.now())
    error_entities = []
    async with TelegramClient('session', API_ID, API_HASH) as client:
        for e in entry.to:
            try:
                await client.get_input_entity(e)
            except ValueError as _:
                error_entities.append(e)
    if len(error_entities) != 0:
        await bot.reply_to(message, "Этих пользователей, групп или каналов не существует:\n{}\nПопробуйте снова"
                           .format("\n".join(error_entities)))
        await bot.delete_state(message.from_user.id, message.chat.id)
        return
    user_data[username].append(entry)
    await bot.reply_to(message, "Запись успешна добавлена и рассылка началась")
    await bot.delete_state(message.from_user.id, message.chat.id)


@bot.message_handler(commands=['add'])
async def add(message):
    if not is_authed(message):
        return
    await bot.set_state(message.from_user.id, MyStates.adding_entry, message.chat.id)
    await bot.reply_to(message,
                       "Введите ссылку/ссылки на пользователя вида `@Username` или ссылку/ссылки на группы вида "
                       "`https://t.me/group`. К каждой группе ссылок прикрепите определенный текст, написал его "
                       "после пустой строки. Если после еще одной пустой строки в конце написать число, то это будет "
                       "зажержка между отправкой для этой записи (если не писать число в конце будет взято значение "
                       "по умолчанию).")


@bot.message_handler(commands=['show'])
async def show_entries(message):
    if not is_authed(message):
        return
    username = message.from_user.username
    entries = user_data[username]
    if len(entries) == 0:
        await bot.reply_to(message, "У вас пока нет записей")
        return
    ans = ""
    for k, v in enumerate(entries):
        ans += "{}) *Получатели*:\n{}\n*Текст*:\n{}\n*Задержка*(в минутах): {}\n\n".format(k, "\n".join(v.to), v.text,
                                                                                           v.delay)
    await bot.reply_to(message, ans)


@bot.message_handler(commands=['delete'])
async def show_entries(message):
    if not is_authed(message):
        return
    username = message.from_user.username
    entries = user_data[username]
    if len(entries) == 0:
        await bot.reply_to(message, "У вас пока нет записей")
        return
    num = message.text.strip().split(' ', 1)
    if len(num) < 2:
        await bot.reply_to(message, "Укажите какую запись удалить")
        return
    if not num[1].isdigit():
        await bot.reply_to(message, "Укажите число")
        return
    num = int(num[1])
    if num >= len(entries):
        await bot.reply_to(message, "Записи с таким номером нет")
        return
    entries.pop(num)
    await bot.reply_to(message, "Запись успешно удалена")


@bot.message_handler(commands=['start', 'help'])
async def send_welcome(message):
    if is_authed(message):
        await bot.reply_to(message, "- Посмотреть записи - /show\n- Добавить запись - /add\n- Удалить запись - /delete")
    else:
        await bot.reply_to(message, "Это бот для рассылок. Чтобы начать авторизируйтесь командой /auth {ID}")


bot.add_custom_filter(asyncio_filters.StateFilter(bot))


async def add_tips():
    await bot.delete_my_commands(scope=None, language_code=None)

    await bot.set_my_commands(
        commands=[
            telebot.types.BotCommand("/start", "Приветсвенное сообщение"),
            telebot.types.BotCommand("/auth", "Залогиниться"),
            telebot.types.BotCommand("/help", "Полезная ифнормация"),
        ],
    )


async def main_run():
    await asyncio.gather(bot.polling(), add_tips(), send_loop())


asyncio.run(main_run())
